package cn.unknowpupil.common.security.component;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.UUID;
import cn.unknowpupil.common.core.constant.SecurityConstants;
import cn.unknowpupil.common.core.enums.ResultStatus;
import cn.unknowpupil.common.core.exceptions.HealthException;
import cn.unknowpupil.common.core.utils.AddressUtils;
import cn.unknowpupil.common.core.utils.ServletUtils;
import cn.unknowpupil.common.core.utils.StringUtils;
import cn.unknowpupil.common.core.utils.ip.IPUtils;
import cn.unknowpupil.common.redis.utils.RedisUtil;
import cn.unknowpupil.common.security.dto.LoginUser;
import cn.unknowpupil.common.security.properties.SecurityProperties;
import eu.bitwalker.useragentutils.UserAgent;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import static java.util.Objects.isNull;

/**
 * Created at 2020/10/19 by w10g <br>
 * Initialize TokenService ... <br>
 *
 * @author unknowpupil
 * @since 0.0.1
 */
@Slf4j
@Component
public class TokenService {
    @Autowired
    private SecurityProperties securityProperties;



    protected static final long MILLIS_SECOND = 1000;

    protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;

    private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;

    public static final String AUTHORITIES_KEY = "auth";


    @Autowired
    private RedisUtil redisUtil;

    private JwtParser jwtParser;

    private JwtBuilder jwtBuilder;


    /**
     * 获取用户身份信息
     *
     * @return 用户信息
     */
    public LoginUser getLoginUser(String token) {

        SecretKey secretKey = getSecretKey();
        Jws<Claims> jws = Jwts.parserBuilder()
                // 解析 JWT 的服务器与创建 JWT 的服务器的时钟不一定完全同步，此设置允许两台服务器最多有 3 分钟的时差
                .setAllowedClockSkewSeconds(180L)
                .setSigningKey(secretKey)
                .build().parseClaimsJws(token);
        Claims body = jws.getBody();
        Object o = body.get(AUTHORITIES_KEY);
        if(isNull(o)){
            throw new  HealthException(ResultStatus.UNAUTHORIZED);
        }
        LoginUser user =  (LoginUser)redisUtil.get(securityProperties.getOnlineKey()+o.toString());

        return user;


    }


    /**
     * 获取请求token
     *
     * @param request
     * @return token
     */
    public String getToken(HttpServletRequest request) {
        String token = request.getHeader(SecurityConstants.HEADER);
        if (StringUtils.isNotEmpty(token) && token.startsWith(SecurityConstants.TOKEN_PREFIX)&&token!="undefined") {
            return  token.replace(SecurityConstants.TOKEN_PREFIX, "");
        }
        return null;
    }

    /**
     * 创建Token 设置永不过期，
     * Token 的时间有效性转到Redis 维护
     *
     * @param authentication /
     * @return /
     */
    public String createToken(LoginUser authentication) {
        String token = UUID.fastUUID().toString();
        String jwt = geneJsonWebToken(token);
        authentication.setToken(token);
        // 设置多段登录
        Object key = redisUtil.get(securityProperties.getMultilateralKey() + authentication.getId());
        if (!isNull(key)) {
            // 说明用户已经登录修改用户状态 设置被强行登录
            Object o = redisUtil.get(securityProperties.getOnlineKey() + key.toString());
            if(!isNull(o)){
                LoginUser user =redisUtil.getCacheObject(securityProperties.getOnlineKey() + key.toString());
                user.setMultilateralLogin(true);
                user.setLoginTime(System.currentTimeMillis());
                setUserAgent(user);
                boolean set = redisUtil.set(securityProperties.getOnlineKey() + user.getToken(), user, securityProperties.getRenew(), TimeUnit.MINUTES);

            }


        }
        authentication.setMultilateralLogin(false);
        setUserAgent(authentication);
        refreshToken(authentication);

        return jwt;
    }


    /**
     * 根据用户信息，生成令牌
     *
     * @return
     */
    private String geneJsonWebToken(String token) {


        Map<String, Object> claims = new HashMap<>();


        claims.put(AUTHORITIES_KEY, token);
        // 添加自定义参数
        JwtBuilder jwtBuilder = Jwts.builder()
                .setClaims(claims);
//        long currentTimeMillis = System.currentTimeMillis();
        // 永不过期 设置过期时间
//        jwtBuilder.setExpiration(new Date(currentTimeMillis + TOKEN_EXPIRED_SECOND * 1000));

        SecretKey secretKey = getSecretKey();
        jwtBuilder.signWith(secretKey);

        return jwtBuilder.compact();

    }

    /**
     * SecretKey 根据 SECRET 的编码方式解码后得到：
     * Base64 编码：SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretString));
     * Base64URL 编码：SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64URL.decode(secretString));
     * 未编码：SecretKey key = Keys.hmacShaKeyFor(secretString.getBytes(StandardCharsets.UTF_8));
     */
    private SecretKey getSecretKey() {
        byte[] encodeKey = Decoders.BASE64.decode(securityProperties.getSecret());
        return Keys.hmacShaKeyFor(encodeKey);
    }


    /**
     * 设置用户代理信息
     *
     * @param loginUser 登录信息
     */
    private void setUserAgent(LoginUser loginUser) {
        UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
        String ip = IPUtils.getIpAddr(ServletUtils.getRequest());
        loginUser.setIpaddr(ip);
        loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
        loginUser.setBrowser(userAgent.getBrowser().getName());
        loginUser.setOs(userAgent.getOperatingSystem().getName());
    }

    /**
     * 验证令牌有效期，相差不足20分钟，自动刷新缓存
     *
     * @param loginUser
     * @return 令牌
     */
    public void verifyToken(LoginUser loginUser) {

        long expireTime = loginUser.getExpireTime();
        long currentTime = System.currentTimeMillis();
        if (expireTime - currentTime <= securityProperties.getDetect()) {
            refreshToken(loginUser);
        }
    }

    /**
     * 刷新token 相当于重新赋值 目的是因为要修改存储token里面的值。
     *
     * @param loginUser
     */
    public void refreshToken(LoginUser loginUser) {
        if(loginUser.getLoginTime()==null){
            loginUser.setLoginTime(System.currentTimeMillis());
        }
        loginUser.setExpireTime(System.currentTimeMillis() + securityProperties.getRenew() * MILLIS_MINUTE);
        redisUtil.set(securityProperties.getMultilateralKey()+loginUser.getId(),loginUser.getToken());
        redisUtil.set(securityProperties.getOnlineKey()+loginUser.getToken(), loginUser, securityProperties.getRenew(), TimeUnit.MINUTES);
    }

    /**
     * 未使用
     *
     * @param loginUser 需要检查的token
     */
    private void checkRenewal(LoginUser loginUser) {
        // 判断是否续期token,计算token的过期时间
        long time = redisUtil.getExpire(securityProperties.getOnlineKey() + loginUser.getToken()) * 1000;
        Date expireDate = DateUtil.offset(new Date(), DateField.MILLISECOND, (int) time);
        // 判断当前时间与过期时间的时间差
        long differ = expireDate.getTime() - System.currentTimeMillis();
        // 如果在续期检查的范围内，则续期
        if (differ <= securityProperties.getDetect()) {
            long renew = time + securityProperties.getRenew();
            redisUtil.expire(securityProperties.getOnlineKey() + loginUser.getToken(), renew, TimeUnit.MILLISECONDS);
        }
    }


    public void logout(String token) {
        log.info("【用户退出登录】===Token:{}",token);
        redisUtil.del(securityProperties.getOnlineKey()+token);
    }
}
